package core

import (
	"fmt"
	"math/rand"
	"mmo_game_zinx/pb"
	"sync"
	"yanx/yiface"

	"google.golang.org/protobuf/proto"
)

// 玩家对象
type Player struct {
	// 玩家ID
	Pid int32
	// 当前玩家的连接，用于和客户端通信
	Conn yiface.IConnection
	// 平面x的坐标
	X float32
	// 高度
	Y float32
	// 平面的y坐标(注意不是Y)
	Z float32
	// 旋转的0-360角度
	V float32
}

var PidGen int32 = 1  // 用来生成玩家的计数器
var IdLock sync.Mutex // 保护PidGen的Mutex

// 玩家的构造函数
func NewPlayer(conn yiface.IConnection) *Player {
	// 生成一个玩家ID
	IdLock.Lock()
	id := PidGen
	PidGen++
	IdLock.Unlock()
	// 创建一个玩家对象
	p := &Player{
		Pid:  id,
		Conn: conn,
		X:    float32(160 + rand.Intn(10)), // 随机在160坐标点，基于X轴偏移
		Y:    0,
		Z:    float32(140 + rand.Intn(20)), // 随机在140坐标点，基于Y轴偏移
		V:    0,                            // 默认角度为0
	}
	return p
}

/*
	提供一个发送给客户端消息的方法
	主要是将pb的protobuf数据序列化之后，再调用yanx的SendMsg方法
*/
func (p *Player) SendMsg(msgID uint32, data proto.Message) {
	// 将proto Message结构体序列化，转换成二进制
	msg, err := proto.Marshal(data)
	if err != nil {
		fmt.Println("Marshal msg err: ", err)
		return
	}

	// 将二进制文件 通过yanx框架的sendmsg将数据发送给客户端
	if p.Conn == nil {
		fmt.Println("connection in player is nil")
		return
	}

	if err := p.Conn.SendMsg(msgID, msg); err != nil {
		fmt.Println("Player sendmsg error!")
		return
	}
	return
}

// 告知客户端玩家的Pid，同步已经生成的玩家ID给客户端
func (p *Player) SyncPid() {
	// 组建MsgID：0 的proto数据
	proto_msg := &pb.SyncPid{
		Pid: p.Pid,
	}
	// 将消息发送给客户端
	p.SendMsg(1, proto_msg)
}

// 广播玩家自己的出生地点
func (p *Player) BroadCastStartPosition() {
	// 组建MsgID：0 的proto数据
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  2,
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.Z,
			},
		},
	}
	// 将消息发送给客户端
	p.SendMsg(200, proto_msg)
}

// 玩家广播世界聊天消息
func (p *Player) Talk(content string) {
	// 1 组建MsgId：200 proto数据
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  1,
		Data: &pb.BroadCast_Content{
			Content: content,
		},
	}
	// 2 得到当前世界所有的在线玩家
	players := WorldMgrObj.GetAllPlayers()

	// 3 向所有的玩家（包括自己）发送msgID：200消息
	for _, player := range players {
		// player分别给对应的客户端发送消息
		player.SendMsg(200, proto_msg)
	}
}

// 同步玩家上线位置信息
func (p *Player) SyncSurrounding() {
	// 1 根据自己的位置，获取周围九宫格内的玩家pid
	pids := WorldMgrObj.AoiMgr.GetPidsByPos(p.X, p.Z)
	players := make([]*Player, 0, len(pids))
	for _, pid := range pids {
		players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
	}
	// 2 将当前玩家的位置信息通过MsgID:200，发送给周围的玩家（让别人看到自己）
	// 2.1 组建msgID:200 proto数据
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  2, // tp2 代表广播坐标
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}

	// 2.2 向周围格子的全部玩家的客户端发送200信息，proto_msg
	for _, player := range players {
		player.SendMsg(200, proto_msg)
	}
	// 3 将周围格子的全部玩家信息发送给当前客户端 包括自己
	// 3.1 组建msgID:202 proto数据
	// 3.1.1制作pb.player slice
	players_proto_msg := make([]*pb.Player, 0, len(players))
	for _, player := range players {
		// 制作一个message Player
		p := &pb.Player{
			Pid: player.Pid,
			P: &pb.Position{
				X: player.X,
				Y: player.Y,
				Z: player.Z,
				V: player.V,
			},
		}
		players_proto_msg = append(players_proto_msg, p)
	}
	// 3.1.2 封装SyncPlayer protobuf数据
	syncPlayer_proto_msg := &pb.SyncPlayers{
		Ps: players_proto_msg,
	}
	// 3.2 将组建好的数据发送给当前玩家的客户端
	p.SendMsg(202, syncPlayer_proto_msg)
}

// 广播当前玩家的位置移动信息
func (p *Player) UpdatePos(x, y, z, v float32) {
	// 1 更新当前玩家的位置信息
	p.X = x
	p.Y = y
	p.Z = z
	p.V = v
	// 2 组件广播proto协议 MsgID：200 Tp-4
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  4, // 4表示移动之后的坐标信息
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: x,
				Y: y,
				Z: z,
				V: v,
			},
		},
	}
	// 3 获取当前玩家的周边玩家AOI九宫格之内的玩家
	players := p.GetSuroundPlayers()

	// 依次给每个玩家对应的客户端发送当前玩家位置更新的信息
	for _, player := range players {
		player.SendMsg(200, proto_msg)
	}
}

// 获取当前玩家的周边玩家AOI九宫格之内的玩家
func (p *Player) GetSuroundPlayers() []*Player {
	// 获取当前玩家九宫格之内的玩家pid
	pids := WorldMgrObj.AoiMgr.GetPidsByPos(p.X, p.Z)

	// 将所有的pid对应的player放到Player切片中
	players := make([]*Player, 0, len(pids))
	for _, pid := range pids {
		players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
	}
	return players
}

// 玩家下线业务
func (p *Player) Offline() {
	// 1 获取当前玩家周边九宫格的玩家
	players := p.GetSuroundPlayers()
	// 2 向周边玩家广播MsgID201信息（下线信息）
	proto_msg := &pb.SyncPid{
		Pid: p.Pid,
	}

	for _, player := range players {
		player.SendMsg(201, proto_msg)
	}

	// 3 将当前玩家从世界管理器和AOI管理器中删除
	WorldMgrObj.AoiMgr.RemoveFromGridByPos(int(p.Pid), p.X, p.Z)
	WorldMgrObj.RemovePlayerByPid(p.Pid)
}
